#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <ifaddrs.h>
#include <pthread.h>

#include "ping_interface_common.h"

int timing = 1;			/* flag to do timing */

int set_ping_opt(ping_configuration_packet* pst_ping_config)
{
	struct hostent *hp = NULL;
	char hnamebuf[MAX_HOSTNAMELEN];
	
	pst_ping_config->preload = 1;
	if (pst_ping_config->icmp_sock == 0)
	{
		pst_ping_config->icmp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
		if (pst_ping_config->icmp_sock < 0) {
        	__log_error("socket");
			return -1;
    	}
	}
	
	/*configuration destination*/
	memset((char *)&pst_ping_config->destination, 0, sizeof(pst_ping_config->destination));
	
	pst_ping_config->destination.sin_family = AF_INET;
    if (inet_aton(pst_ping_config->url, &pst_ping_config->destination.sin_addr) == 1) {
        pst_ping_config->hostname = pst_ping_config->url;      //??????????????????????
        __log_debug("hostname = %s\n", pst_ping_config->hostname);
    } else {
        char *idn;
        idn = pst_ping_config->url;
        hp = gethostbyname(idn);
        if (!hp) {
			__log_error("ping: unknown host %s\n", idn);
            return -2;
        }
		memcpy(&pst_ping_config->destination.sin_addr, hp->h_addr, 4);
		strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1);
    	hnamebuf[sizeof(hnamebuf) - 1] = 0;
		pst_ping_config->hostname = hnamebuf;
		__log_debug("hostname = %s\n", pst_ping_config->hostname);
    }

	/*configuration source*/
	pst_ping_config->source.sin_family = AF_INET;

	if (pst_ping_config->source.sin_addr.s_addr == 0) {
        socklen_t alen;
        struct sockaddr_in dst = pst_ping_config->destination;
	
        int probe_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (probe_fd < 0) {
            __log_error("socket");
            exit(2);
        }
		
        if (pst_ping_config->device) {
            struct ifreq ifr;
            int rc;
            memset(&ifr, 0, sizeof(ifr));
            strncpy(ifr.ifr_name, pst_ping_config->device, IFNAMSIZ-1);
			
            modify_capability(1, pst_ping_config->euid);
			
            rc = setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, 
					pst_ping_config->device, strlen(pst_ping_config->device)+1);
            modify_capability(0, pst_ping_config->euid);
			
            if (rc == -1) {
				uint32_t host_sequence = ntohl(dst.sin_addr.s_addr);
				//__log_debug("pingTest:host_sequence =%d, dst.sin_addr.s_addr = %d\n",
				//								host_sequence ,dst.sin_addr.s_addr);
			
                if (IN_MULTICAST(host_sequence)) {
                    struct ip_mreqn imr;
					
                    if (ioctl(probe_fd, SIOCGIFINDEX, &ifr) < 0) {
                    	__log_error("ping: unknown iface %s\n", pst_ping_config->device);
                        exit(2);
                    }
					
                    memset(&imr, 0, sizeof(imr));
                    imr.imr_ifindex = ifr.ifr_ifindex;
                    if (setsockopt(probe_fd, SOL_IP, IP_MULTICAST_IF, &imr, sizeof(imr)) == -1) {
                        __log_error("ping: IP_MULTICAST_IF");
                        exit(2);
                    }
					
                } else {
                    __log_error("ping: SO_BINDTODEVICE");
                    exit(2);
                }
            }
        }
		
        dst.sin_port = htons(1025);
		/*       !!!!!!!!!!!!!!!!!!!!!!!!!
			if (nroute) {
            	dst.sin_addr.s_addr = route[0];
        	}
		*/

		/*		!!!!!!!!!!!!!!!!!!!!
        	sock_setmark(probe_fd);
		*/

		/*options ping the broadcast*/
        if (connect(probe_fd, (struct sockaddr*)&dst, sizeof(dst)) == -1) {
			extern int errno;
            if (errno == EACCES) {
                if (pst_ping_config->broadcast_pings == 0) {
                   __log_error("Do you want to ping broadcast? Then -b\n");
                    exit(2);
                }
              	__log_debug("WARNING: pinging broadcast address\n");
                if (setsockopt(probe_fd, SOL_SOCKET, SO_BROADCAST, &pst_ping_config->broadcast_pings,
														sizeof(pst_ping_config->broadcast_pings)) < 0) {
                    __log_error("can't set broadcasting");
                    exit(2);
                }
                if (connect(probe_fd, (struct sockaddr*)&dst, sizeof(dst)) == -1) {
                    __log_error("connect");
                    exit(2);
                }
            } else {
                __log_error("connect");
                exit(2);
            }
			
        }

		
        alen = sizeof(pst_ping_config->source);
        if (getsockname(probe_fd, (struct sockaddr*)&pst_ping_config->source, &alen) == -1) {
            __log_error("getsockname");
            exit(2);
        }
		
        pst_ping_config->source.sin_port = 0;

        if (pst_ping_config->device) {
            struct ifaddrs *ifa0, *ifa;
            int ret;
		
            ret = getifaddrs(&ifa0);
			
            if (ret) {
                __log_error("gatifaddrs() failed.\n");
                exit(2);
            }
			
            for (ifa = ifa0; ifa; ifa = ifa->ifa_next) {
				//__log_debug("we can find the devices:%s\n", ifa->ifa_name);
                if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
                    continue;
				__log_debug("result = %d + %d\n", 
				strncmp(ifa->ifa_name, pst_ping_config->device, strlen(pst_ping_config->device)-1),
				memcmp(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
                            &pst_ping_config->source.sin_addr, sizeof(pst_ping_config->source.sin_addr)));
                if (!strncmp(ifa->ifa_name, pst_ping_config->device, strlen(pst_ping_config->device)-1) &&
                    !memcmp(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
                            &pst_ping_config->source.sin_addr, sizeof(pst_ping_config->source.sin_addr))) {
                    __log_debug("\n");
                    break;
                }

            }
			//__log_debug("\n");
            freeifaddrs(ifa0);
			//__log_debug("\n");
            if (!ifa)
                __log_debug("ping: Warning: source address might be selected on device other than %s.\n",
                					pst_ping_config->device);
        }
        close(probe_fd);
    }


	//__log_debug("\n");
	if (pst_ping_config->destination.sin_addr.s_addr == 0) {
		pst_ping_config->destination.sin_addr.s_addr = pst_ping_config->source.sin_addr.s_addr;
	}
	//__log_debug("\n");
	
	if (pst_ping_config->icmp_sock < 0) {
		//int socket_errno = 0;
		//errno = socket_errno;
		__log_debug("ping: icmp open socket");
		exit(2);
	}

	//__log_debug("\n");
	if (pst_ping_config->device) {
        struct ifreq ifr;
        memset(&ifr, 0, sizeof(ifr));
        strncpy(ifr.ifr_name, pst_ping_config->device, IFNAMSIZ-1);
        if (ioctl(pst_ping_config->icmp_sock, SIOCGIFINDEX, &ifr) < 0) {
            __log_error("ping: unknown iface %s\n", pst_ping_config->device);
            exit(2);
        }

		pst_ping_config->cmsg.cm.cmsg_len = sizeof(struct cmsghdr) + sizeof(struct in_pktinfo);
		pst_ping_config->cmsg.cm.cmsg_level = SOL_IP;
		pst_ping_config->cmsg.cm.cmsg_type = IP_PKTINFO;
		pst_ping_config->cmsg.ipi.ipi_ifindex = ifr.ifr_ifindex;
		//st_ping_config->cmsg_len = sizeof(cmsg);
    }
	//__log_debug("\n");
	if (pst_ping_config->broadcast_pings || 
			IN_MULTICAST(ntohl(pst_ping_config->destination.sin_addr.s_addr))) {
        if (pst_ping_config->uid) {
            if (pst_ping_config->interval < 1000) {
                __log_error("ping: broadcast ping with too short interval.\n");
                exit(2);
            }
            if (pst_ping_config->pmtudisc >= 0 && pst_ping_config->pmtudisc != IP_PMTUDISC_DO) {
                __log_error("ping: broadcast ping does not fragment.\n");
                exit(2);
            }
        }
        if (pst_ping_config->pmtudisc < 0)
            pst_ping_config->pmtudisc = IP_PMTUDISC_DO;
    }
	//__log_debug("\n");
	if (pst_ping_config->pmtudisc >= 0) {
        if (setsockopt(pst_ping_config->icmp_sock, SOL_IP, IP_MTU_DISCOVER, 
				&pst_ping_config->pmtudisc, sizeof(pst_ping_config->pmtudisc)) == -1) {
            __log_error("ping: IP_MTU_DISCOVER");
            exit(2);
        }
    }
	//__log_debug("\n");
	int hold = 1;
    if (setsockopt(pst_ping_config->icmp_sock, SOL_IP, IP_RECVERR, (char *)&hold, sizeof(hold)))
       __log_error("WARNING: your kernel is veeery old. No problems.\n");
    if (setsockopt(pst_ping_config->icmp_sock, SOL_IP, IP_RECVTTL, (char *)&hold, sizeof(hold)))
       __log_error("WARNING: setsockopt(IP_RECVTTL)");
    if (setsockopt(pst_ping_config->icmp_sock, SOL_IP, IP_RETOPTS, (char *)&hold, sizeof(hold)))
       __log_error("WARNING: setsockopt(IP_RETOPTS)");


	/* Estimate memory eaten by single packet. It is rough estimate.
     * Actually, for small datalen's it depends on kernel side a lot. */
    hold = pst_ping_config->datalen + 8;
    hold += ((hold+511)/512)*(pst_ping_config->optlen + 20 + 16 + 64 + 160);
    sock_setbufs(pst_ping_config, hold);

    if (setsockopt(pst_ping_config->icmp_sock, SOL_SOCKET, SO_BROADCAST,
                       &pst_ping_config->broadcast_pings, sizeof(pst_ping_config->broadcast_pings)) < 0) {
       __log_error("ping: can't set broadcasting");
       exit(2);
    }
					   
	if (pst_ping_config->datalen > 0xFFFF - 8 - pst_ping_config->optlen - 20) {
	   if (pst_ping_config->uid || pst_ping_config->datalen > sizeof(pst_ping_config->outpack)-8) {
		   __log_error("Error: packet size %d is too large. Maximum is %d\n",
		   				pst_ping_config->datalen, 0xFFFF-8-20-pst_ping_config->optlen);
		   exit(2);
	   }
	   /* Allow small oversize to root yet. It will cause EMSGSIZE. */
	   __log_error("WARNING: packet size %d is too large. Maximum is %d\n", 
	   					pst_ping_config->datalen, 0xFFFF-8-20-pst_ping_config->optlen);
	}

	setup(pst_ping_config);
	return 0;
}

void limit_capabilities(int* uid, int* euid)
{
    *uid = getuid();
    *euid = geteuid();
    if (seteuid(*uid)) {
        perror("ping: setuid");
        exit(-1);
    }
}

void default_ping_configuration(ping_configuration_packet* pst_ping_config) 
{
//	memset(pst_ping_config, 0, sizeof(pst_ping_config));
	pst_ping_config->ping_status.pipesize 	= -1;
	pst_ping_config->ping_status.tmin 		= LONG_MAX;
	pst_ping_config->interval 				= 1000;
	pst_ping_config->pmtudisc 				= -1;
	pst_ping_config->datalen 				= DEFDATALEN;
	pst_ping_config->lingertime 			= MAXWAIT*1000;
	pst_ping_config->ping_status.isRunning  = 0;
}

int  ping_set(ping_configuration_packet* pst_ping_config)
{
	default_ping_configuration(pst_ping_config);
	/*1. */
	limit_capabilities(&pst_ping_config->uid, &pst_ping_config->uid);

	int ret = set_ping_opt(pst_ping_config);
	if (ret != 0) {
		__log_debug("set_ping_opt failed \n");
		return -1;
	}	
	return 0;
}


int do_ping(ping_configuration_packet* pst_ping_config, st_handler_packet* phandler)
{
	int packlen;
	u_char *packet;
	__log_error("\n");
	packlen = pst_ping_config->datalen + MAXIPLEN + MAXICMPLEN;
	if (!(packet = (u_char *)malloc((u_int)packlen))) {
		__log_error("ping: out of memory.\n");
		return -1;
	}
	__log_error("\n");	
	if (pst_ping_config->datalen >= sizeof(struct timeval))	/* can we time transfer */
		timing = 1;
	__log_error("\n");
	main_loop(pst_ping_config, packet, packlen, phandler);
	__log_error("\n");
	free(packet);
	
	return 0;
}



#if 0

void* afunc(void* p)
{
        __log_debug("p = %s \n", (char *)p);
        return NULL;
}


static void *fun_9607(void *arg)
{
	int ret;
	ping_configuration_packet ping_packet_9607;
	__log_debug("I'm thread, Thread ID = %lu\n", pthread_self());

	default_ping_configuration(&ping_packet_9607);

	/*1. */
	limit_capabilities(&ping_packet_9607.uid, &ping_packet_9607.uid);
	ping_packet_9607.npackets = 5;
	//strncpy(ping_packet.device, "ppp0", strlen("ppp0"));
	//strncpy(ping_packet.url, "www.baidu.com", strlen("www.baidu.com"));
	ping_packet_9607.device = "ppp0";
	ping_packet_9607.url = "www.baidu.com";

	/*2 set socket opt*/
	ret = set_ping_opt(&ping_packet_9607);
	if (ret != 0) {
		__log_debug("set_ping_opt failed \n");
		return NULL;
	}

	int packlen;
	u_char *packet;
	char testbuf[128] = "hello 9607";
	
	packlen = ping_packet_9607.datalen + MAXIPLEN + MAXICMPLEN;
	if (!(packet = (u_char *)malloc((u_int)packlen))) {
		__log_error("ping: out of memory.\n");
		return NULL;
	}

	if (ping_packet_9607.datalen >= sizeof(struct timeval))	/* can we time transfer */
		timing = 1;

	st_handler_packet handler = { 
		.handler_func = afunc,
		.arg = testbuf
		};

	main_loop(&ping_packet_9607, packet, packlen, &handler);

	return NULL;
}

static void *fun_660(void *arg)
{
	int ret;
	ping_configuration_packet ping_packet_660;
	__log_debug("I'm thread, Thread ID = %lu\n", pthread_self());

	default_ping_configuration(&ping_packet_660);

	/*1. */
	limit_capabilities(&ping_packet_660.uid, &ping_packet_660.uid);
	ping_packet_660.npackets = 5;
	//strncpy(ping_packet.device, "ppp0", strlen("ppp0"));
	//strncpy(ping_packet.url, "www.baidu.com", strlen("www.baidu.com"));
	ping_packet_660.device = "rmnet_data0";
	ping_packet_660.url = "www.baidu.com";

	/*2 set socket opt*/
	ret = set_ping_opt(&ping_packet_660);
	if (ret != 0) {
		__log_debug("set_ping_opt failed \n");
		return NULL;
	}
	
	
	
	int packlen;
	u_char *packet;
	char testbuf[128] = "hello 660";
	
	packlen = ping_packet_660.datalen + MAXIPLEN + MAXICMPLEN;
	if (!(packet = (u_char *)malloc((u_int)packlen))) {
		__log_error("ping: out of memory.\n");
		return NULL;
	}
	
	if (ping_packet_660.datalen >= sizeof(struct timeval))	/* can we time transfer */
		timing = 1;
	
	st_handler_packet handler = { 
		.handler_func = afunc,
		.arg = testbuf
		};
	
	main_loop(&ping_packet_660, packet, packlen, &handler);

	
	return NULL;
}


void* afunc(void* p)
{
        __log_debug("-------------!!!!!!!!!!!!!!!1------------------------- \n");
        return NULL;
}


struct recv_msg{
	int 	msg_id;
	char* 	entity;
	char* 	target;
};

struct handle_send_msg{
	struct recv_msg 	msg;
	int 				socketfd;
};


static ping_configuration_packet* g_conf_660 = NULL;

static st_handler_packet handler_660 = {
	.handler_func = afunc,
};

int main(void)
{
	//int ret = ping_set(g_conf_660);	
	g_conf_660 = (ping_configuration_packet *)malloc(sizeof(ping_configuration_packet));
	if (g_conf_660 == NULL) {
		__log_error("ping: out of memory.\n");
		//send_buf = "id-111,entity-out of memory,target-660|";
	}
	g_conf_660->npackets = 5;
	g_conf_660->device = "ppp0";
	g_conf_660->url	= "baidu.com";
	int ret = ping_set(g_conf_660);
	if (ret) {
		//send_buf = "id-111,entity-no,target-660|";
		__log_error("ping_set failed\n");
		//return -1;
	} else {
		//send_buf = "id-111,entity-yes,target-660|";
	}



	struct handle_send_msg h660;
	h660.msg.msg_id = 222;
	h660.msg.target = "660";
	//h660.socketfd = connect_fd;
	handler_660.arg = (void *)&h660;
	
	do_ping(g_conf_660, &handler_660);

	
	return 0;
}
#else



#endif


